﻿//======================================================================
//
//	Cross-Platform XNA Menu System
//  http://cpxms.codeplex.com/
//
//  All code and content licensed under Microsoft Public License (Ms-PL)	
//
//======================================================================
//
//	File:		Clipboard.cs
//
//	Author:		MJP
//
//	Date:		04/26/09
//
//======================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Xml.Serialization;
using System.Reflection;
using System.Collections.ObjectModel;

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

using CPXMenu;

namespace CPXMenuEditor
{
	/// <summary>
	/// Manages copy, cut, and paste for the editor.
	/// </summary>
    public class Clipboard
    {
        private ActionManager actionManager;
        private List<ClipboardItem> clipboardContents = new List<ClipboardItem>();

		/// <summary>
		/// Gets the ActionManager used for performing actions
		/// </summary>
        public ActionManager ActionManager
        {
            get { return actionManager; }
        }

		/// <summary>
		/// Gets the collection of ClipboardItem's on the clipboard
		/// </summary>
        public ReadOnlyCollection<ClipboardItem> ClipboardContents
        {
            get { return clipboardContents.AsReadOnly(); }
        }

		/// <summary>
		/// Creates a new Clipboard instance, targeting the specified ActionManager.
		/// </summary>
		/// <param name="actionManager">The ActionManager to perform actions with</param>
        public Clipboard(ActionManager actionManager)
        {
            this.actionManager = actionManager;

			actionManager.ProjectChanged += new ProjectChangedDelegate(actionManager_ProjectChanged);
        }		

		/// <summary>
		/// Copies all currently-selected items to the clipboard
		/// </summary>
        public void Copy()
        {
            if (actionManager.SelectedItems.Count == 0)
                return;

            Clear();

            List<MenuItem> itemsToCopy = new List<MenuItem>();
            foreach (MenuItem item in actionManager.SelectedItems)
                if (item.CanCopyPaste)
                    itemsToCopy.Add(item);

            PurgeChildItems(itemsToCopy);

            AddToClipboard(itemsToCopy);

            OnCopy();
        }

		/// <summary>
		/// Removes all currently-selected items, and adds them to the clipboard
		/// </summary>
        public void Cut()
        {
            if (actionManager.SelectedItems.Count == 0)
                return;

            Clear();

            List<MenuItem> itemsToCut = new List<MenuItem>();
            foreach (MenuItem item in actionManager.SelectedItems)
                if (item.CanCopyPaste)
                    itemsToCut.Add(item);

            PurgeChildItems(itemsToCut);

            ActionManager.RemoveItems(itemsToCut.ToArray());
            AddToClipboard(itemsToCut);

            OnCut();
        }

		/// <summary>
		/// Pastes all items from the clipboard to the project
		/// </summary>
        public void Paste()
        {
            if (clipboardContents.Count == 0)
                return;

            // We need to figure out where to stick everything.  First, make sure
            // there's a current page since everything has to go onto a page.
            Page currentPage = actionManager.MenuProject.CurrentPage;
            if (currentPage == null)
                OnMessageNeedsToBeShown("The project needs to have a current page selected for items to be pasted");

            // Is there a single suitable parent item selected?
            MenuItem parent = null;
            if (actionManager.SelectedItems.Count == 1 && actionManager.SelectedItems[0].CanHaveChildren)
                parent = actionManager.SelectedItems[0];

            // If no suitable parent, just stick it on the page
            if (parent == null)
                parent = currentPage;

            // We'll make sure each item is compatible with the parent.
            // If not, we'll skip and notify the user.
            List<Type> typesThatCouldntPaste = new List<Type>();
            List<MenuItem> itemsToAdd = new List<MenuItem>();
            List<MenuItem> parents = new List<MenuItem>();
            foreach (ClipboardItem clipboardItem in clipboardContents)
            {
                if (MenuItem.IsValidParentChildCombination(parent.GetType(), clipboardItem.Item.GetType()))
                {
                    parents.Add(parent);
                    MenuItem clone = clipboardItem.Item.Clone();
					clone.Position = MenuItem.SubtractPoint(MenuItem.AddPoint(parent.CenterPoint, clipboardItem.RelativePosition), parent.AbsolutePosition);
                    itemsToAdd.Add(clone);
                }
                else
                {
                    if (!typesThatCouldntPaste.Contains(clipboardItem.Item.GetType()))
                        typesThatCouldntPaste.Add(clipboardItem.Item.GetType());
                }
            }

            actionManager.AddItems(itemsToAdd.ToArray(), parents.ToArray());

            if (typesThatCouldntPaste.Count > 0)
            {
                string types = "";
                for (int i = 0; i < typesThatCouldntPaste.Count; i++)
                {
                    if (i == 0)
                        types += typesThatCouldntPaste[i].Name;
                    else
                        types += ", " + typesThatCouldntPaste[i].Name;
                }

                OnMessageNeedsToBeShown("Some items could not be pasted because the types are not compatible with the " +
                                        "selected parent item(" + parent.Name + "): " + types);
            }

            OnPaste();
        }

		/// <summary>
		/// Clears the contents of the clipboard
		/// </summary>
        public void Clear()
        {
            clipboardContents.Clear();

			OnClipboardContentsChanged();
        }

		/// <summary>
		/// Called when a Copy occurs.  Raises the ItemsCopied event.
		/// </summary>
        protected virtual void OnCopy()
        {
			OnClipboardContentsChanged();

            if (ItemsCopied != null)
                ItemsCopied(this);
        }

		/// <summary>
		/// Called when a Cut occurs.  Raises the ItemsCut event.
		/// </summary>
        protected virtual void OnCut()
        {
			OnClipboardContentsChanged();

            if (ItemsCut != null)
                ItemsCut(this);
        }

		/// <summary>
		/// Called when a Paste occurs.  Raises the ItemsPasted event.
		/// </summary>
        protected virtual void OnPaste()
        {
            if (ItemsPasted != null)
                ItemsPasted(this);
        }

		/// <summary>
		/// Called when the contents of the clipboard changes
		/// </summary>
		protected virtual void OnClipboardContentsChanged()
		{
			if (ClipboardContentsChanged != null)
				ClipboardContentsChanged(this);
		}

		/// <summary>
		/// Called when a message needs to be displayed to the user.
		/// Raises the MessageNeedsToBeShown event.
		/// </summary>
		/// <param name="message"></param>
        protected virtual void OnMessageNeedsToBeShown(string message)
        {
            if (MessageNeedsToBeShown != null)
                MessageNeedsToBeShown(this, message);
        }

		/// <summary>
		/// Removes all selected child items from the List whose parents
		/// are also selected.
		/// </summary>
		/// <param name="items">The item from which the items will be removed</param>
        private void PurgeChildItems(List<MenuItem> items)
        {
            // We don't want to add child items to the clipboard if one of its parents
            // is selected, since the child item will get pasted anyway.

            foreach (MenuItem item in items)
            {
                MenuItem[] parents = item.GetAllParents();
                foreach (MenuItem parent in parents)
                {
                    if (items.Contains(parent))
                    {
                        items.Remove(item);
                        break;
                    }
                }
            }
        }

		/// <summary>
		/// Adds the list of items to the clipboard
		/// </summary>
		/// <param name="items">The items to add</param>
        private void AddToClipboard(List<MenuItem> items)
        {
            // Figure out the center point of the items
            float minX = float.MaxValue;
            float minY = float.MaxValue;
            float maxX = float.MinValue;
            float maxY = float.MinValue;

            foreach (MenuItem item in items)
            {
                minX = MathHelper.Min(minX, item.AbsolutePosition.X);
                minY = MathHelper.Min(minY, item.AbsolutePosition.Y);

                maxX = MathHelper.Max(maxX, item.AbsolutePosition.X);
                maxY = MathHelper.Max(maxY, item.AbsolutePosition.Y);
            }

            int centerX = (int)((minX + maxX) / 2);
            int centerY = (int)((minY + maxY) / 2);

            // Add 'em to the clipboard
            foreach (MenuItem item in items)
            {
                ClipboardItem clipboardItem = new ClipboardItem();
                clipboardItem.Item = item.Clone();
                clipboardItem.RelativePosition = new Point(item.AbsolutePosition.X - centerX, item.AbsolutePosition.Y - centerY);
                clipboardContents.Add(clipboardItem);
            }            
        }

		void actionManager_ProjectChanged(ActionManager manager, MenuProject oldProject, MenuProject newProject)
		{
			Clear();
		}

		/// <summary>
		/// Raised when a Copy occurs
		/// </summary>
        public event ClipboardDelegate ItemsCopied;

		/// <summary>
		/// Raised when a Cut occurs
		/// </summary>
        public event ClipboardDelegate ItemsCut;

		/// <summary>
		/// Raised when a Paste occurs
		/// </summary>
        public event ClipboardDelegate ItemsPasted;

		/// <summary>
		/// Raised when the contents of the clipboard changes
		/// </summary>
		public event ClipboardDelegate ClipboardContentsChanged;

		/// <summary>
		/// Raised when a message needs to be shown to the user
		/// </summary>
        public event ClipboardMessageDelegate MessageNeedsToBeShown;     
    }

	/// <summary>
	/// A single item on the clipboard.  Used to keep a copy of them
	/// item so that it's state can be copied, and also the position
	/// of the items relative to each other.
	/// </summary>
    public struct ClipboardItem
    {
        public MenuItem Item;
        public Point RelativePosition;

        ClipboardItem(MenuItem item, Point relativePosition)
        {
            Item = item;
            RelativePosition = relativePosition;
        }
    }

    public delegate void ClipboardDelegate(Clipboard clipboard);
    public delegate void ClipboardMessageDelegate(Clipboard clipboard, string message);
}